@intranefr/superbackend 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/.env.example +15 -0
  2. package/README.md +11 -0
  3. package/analysis-only.skill +0 -0
  4. package/index.js +23 -0
  5. package/package.json +8 -2
  6. package/src/admin/endpointRegistry.js +120 -0
  7. package/src/controllers/admin.controller.js +90 -6
  8. package/src/controllers/adminBlockDefinitions.controller.js +127 -0
  9. package/src/controllers/adminBlockDefinitionsAi.controller.js +54 -0
  10. package/src/controllers/adminCache.controller.js +342 -0
  11. package/src/controllers/adminContextBlockDefinitions.controller.js +141 -0
  12. package/src/controllers/adminCrons.controller.js +388 -0
  13. package/src/controllers/adminDbBrowser.controller.js +124 -0
  14. package/src/controllers/adminEjsVirtual.controller.js +13 -3
  15. package/src/controllers/adminExperiments.controller.js +200 -0
  16. package/src/controllers/adminHeadless.controller.js +9 -2
  17. package/src/controllers/adminHealthChecks.controller.js +570 -0
  18. package/src/controllers/adminI18n.controller.js +51 -29
  19. package/src/controllers/adminLlm.controller.js +126 -2
  20. package/src/controllers/adminPages.controller.js +720 -0
  21. package/src/controllers/adminPagesContextBlocksAi.controller.js +54 -0
  22. package/src/controllers/adminProxy.controller.js +113 -0
  23. package/src/controllers/adminRateLimits.controller.js +138 -0
  24. package/src/controllers/adminRbac.controller.js +803 -0
  25. package/src/controllers/adminScripts.controller.js +126 -4
  26. package/src/controllers/adminSeoConfig.controller.js +71 -48
  27. package/src/controllers/blogAdmin.controller.js +279 -0
  28. package/src/controllers/blogAiAdmin.controller.js +224 -0
  29. package/src/controllers/blogAutomationAdmin.controller.js +141 -0
  30. package/src/controllers/blogInternal.controller.js +26 -0
  31. package/src/controllers/blogPublic.controller.js +89 -0
  32. package/src/controllers/experiments.controller.js +85 -0
  33. package/src/controllers/fileManager.controller.js +190 -0
  34. package/src/controllers/fileManagerStoragePolicy.controller.js +23 -0
  35. package/src/controllers/healthChecksPublic.controller.js +196 -0
  36. package/src/controllers/internalExperiments.controller.js +17 -0
  37. package/src/controllers/metrics.controller.js +64 -4
  38. package/src/controllers/orgAdmin.controller.js +80 -0
  39. package/src/helpers/mongooseHelper.js +258 -0
  40. package/src/helpers/scriptBase.js +230 -0
  41. package/src/helpers/scriptRunner.js +335 -0
  42. package/src/middleware/rbac.js +62 -0
  43. package/src/middleware.js +810 -48
  44. package/src/models/BlockDefinition.js +27 -0
  45. package/src/models/BlogAutomationLock.js +14 -0
  46. package/src/models/BlogAutomationRun.js +39 -0
  47. package/src/models/BlogPost.js +42 -0
  48. package/src/models/CacheEntry.js +26 -0
  49. package/src/models/ConsoleEntry.js +32 -0
  50. package/src/models/ConsoleLog.js +23 -0
  51. package/src/models/ContextBlockDefinition.js +33 -0
  52. package/src/models/CronExecution.js +47 -0
  53. package/src/models/CronJob.js +70 -0
  54. package/src/models/Experiment.js +75 -0
  55. package/src/models/ExperimentAssignment.js +23 -0
  56. package/src/models/ExperimentEvent.js +26 -0
  57. package/src/models/ExperimentMetricBucket.js +30 -0
  58. package/src/models/ExternalDbConnection.js +49 -0
  59. package/src/models/FileEntry.js +22 -0
  60. package/src/models/GlobalSetting.js +1 -2
  61. package/src/models/HealthAutoHealAttempt.js +57 -0
  62. package/src/models/HealthCheck.js +132 -0
  63. package/src/models/HealthCheckRun.js +51 -0
  64. package/src/models/HealthIncident.js +49 -0
  65. package/src/models/Page.js +95 -0
  66. package/src/models/PageCollection.js +42 -0
  67. package/src/models/ProxyEntry.js +66 -0
  68. package/src/models/RateLimitCounter.js +19 -0
  69. package/src/models/RateLimitMetricBucket.js +20 -0
  70. package/src/models/RbacGrant.js +25 -0
  71. package/src/models/RbacGroup.js +16 -0
  72. package/src/models/RbacGroupMember.js +13 -0
  73. package/src/models/RbacGroupRole.js +13 -0
  74. package/src/models/RbacRole.js +25 -0
  75. package/src/models/RbacUserRole.js +13 -0
  76. package/src/models/ScriptDefinition.js +1 -0
  77. package/src/models/Webhook.js +2 -0
  78. package/src/routes/admin.routes.js +2 -0
  79. package/src/routes/adminBlog.routes.js +21 -0
  80. package/src/routes/adminBlogAi.routes.js +16 -0
  81. package/src/routes/adminBlogAutomation.routes.js +27 -0
  82. package/src/routes/adminCache.routes.js +20 -0
  83. package/src/routes/adminConsoleManager.routes.js +302 -0
  84. package/src/routes/adminCrons.routes.js +25 -0
  85. package/src/routes/adminDbBrowser.routes.js +65 -0
  86. package/src/routes/adminEjsVirtual.routes.js +2 -1
  87. package/src/routes/adminExperiments.routes.js +29 -0
  88. package/src/routes/adminHeadless.routes.js +2 -1
  89. package/src/routes/adminHealthChecks.routes.js +28 -0
  90. package/src/routes/adminI18n.routes.js +4 -3
  91. package/src/routes/adminLlm.routes.js +4 -2
  92. package/src/routes/adminPages.routes.js +55 -0
  93. package/src/routes/adminProxy.routes.js +15 -0
  94. package/src/routes/adminRateLimits.routes.js +17 -0
  95. package/src/routes/adminRbac.routes.js +38 -0
  96. package/src/routes/adminSeoConfig.routes.js +5 -4
  97. package/src/routes/adminUiComponents.routes.js +2 -1
  98. package/src/routes/blogInternal.routes.js +14 -0
  99. package/src/routes/blogPublic.routes.js +9 -0
  100. package/src/routes/experiments.routes.js +30 -0
  101. package/src/routes/fileManager.routes.js +62 -0
  102. package/src/routes/fileManagerStoragePolicy.routes.js +9 -0
  103. package/src/routes/healthChecksPublic.routes.js +9 -0
  104. package/src/routes/internalExperiments.routes.js +15 -0
  105. package/src/routes/log.routes.js +43 -60
  106. package/src/routes/metrics.routes.js +4 -2
  107. package/src/routes/orgAdmin.routes.js +1 -0
  108. package/src/routes/pages.routes.js +123 -0
  109. package/src/routes/proxy.routes.js +46 -0
  110. package/src/routes/rbac.routes.js +47 -0
  111. package/src/routes/webhook.routes.js +2 -1
  112. package/src/routes/workflows.routes.js +4 -0
  113. package/src/services/blockDefinitionsAi.service.js +247 -0
  114. package/src/services/blog.service.js +99 -0
  115. package/src/services/blogAutomation.service.js +978 -0
  116. package/src/services/blogCronsBootstrap.service.js +185 -0
  117. package/src/services/blogPublishing.service.js +58 -0
  118. package/src/services/cacheLayer.service.js +696 -0
  119. package/src/services/consoleManager.service.js +738 -0
  120. package/src/services/consoleOverride.service.js +7 -1
  121. package/src/services/cronScheduler.service.js +350 -0
  122. package/src/services/dbBrowser.service.js +536 -0
  123. package/src/services/ejsVirtual.service.js +102 -32
  124. package/src/services/experiments.service.js +273 -0
  125. package/src/services/experimentsAggregation.service.js +308 -0
  126. package/src/services/experimentsCronsBootstrap.service.js +118 -0
  127. package/src/services/experimentsRetention.service.js +43 -0
  128. package/src/services/experimentsWs.service.js +134 -0
  129. package/src/services/fileManager.service.js +475 -0
  130. package/src/services/fileManagerStoragePolicy.service.js +285 -0
  131. package/src/services/globalSettings.service.js +15 -0
  132. package/src/services/healthChecks.service.js +650 -0
  133. package/src/services/healthChecksBootstrap.service.js +109 -0
  134. package/src/services/healthChecksScheduler.service.js +106 -0
  135. package/src/services/jsonConfigs.service.js +2 -2
  136. package/src/services/llmDefaults.service.js +190 -0
  137. package/src/services/migrationAssets/s3.js +2 -2
  138. package/src/services/pages.service.js +602 -0
  139. package/src/services/pagesContext.service.js +331 -0
  140. package/src/services/pagesContextBlocksAi.service.js +349 -0
  141. package/src/services/proxy.service.js +535 -0
  142. package/src/services/rateLimiter.service.js +623 -0
  143. package/src/services/rbac.service.js +212 -0
  144. package/src/services/scriptsRunner.service.js +215 -15
  145. package/src/services/uiComponentsAi.service.js +6 -19
  146. package/src/services/workflow.service.js +23 -8
  147. package/src/utils/orgRoles.js +14 -0
  148. package/src/utils/rbac/engine.js +60 -0
  149. package/src/utils/rbac/rightsRegistry.js +33 -0
  150. package/views/admin-blog-automation.ejs +877 -0
  151. package/views/admin-blog-edit.ejs +542 -0
  152. package/views/admin-blog.ejs +399 -0
  153. package/views/admin-cache.ejs +681 -0
  154. package/views/admin-console-manager.ejs +680 -0
  155. package/views/admin-crons.ejs +645 -0
  156. package/views/admin-dashboard.ejs +28 -8
  157. package/views/admin-db-browser.ejs +445 -0
  158. package/views/admin-ejs-virtual.ejs +16 -10
  159. package/views/admin-experiments.ejs +91 -0
  160. package/views/admin-file-manager.ejs +942 -0
  161. package/views/admin-health-checks.ejs +725 -0
  162. package/views/admin-i18n.ejs +59 -5
  163. package/views/admin-llm.ejs +99 -1
  164. package/views/admin-organizations.ejs +163 -1
  165. package/views/admin-pages.ejs +2424 -0
  166. package/views/admin-proxy.ejs +491 -0
  167. package/views/admin-rate-limiter.ejs +625 -0
  168. package/views/admin-rbac.ejs +1331 -0
  169. package/views/admin-scripts.ejs +597 -3
  170. package/views/admin-seo-config.ejs +61 -7
  171. package/views/admin-ui-components.ejs +57 -25
  172. package/views/admin-workflows.ejs +7 -7
  173. package/views/file-manager.ejs +866 -0
  174. package/views/pages/blocks/contact.ejs +27 -0
  175. package/views/pages/blocks/cta.ejs +18 -0
  176. package/views/pages/blocks/faq.ejs +20 -0
  177. package/views/pages/blocks/features.ejs +19 -0
  178. package/views/pages/blocks/hero.ejs +13 -0
  179. package/views/pages/blocks/html.ejs +5 -0
  180. package/views/pages/blocks/image.ejs +14 -0
  181. package/views/pages/blocks/testimonials.ejs +26 -0
  182. package/views/pages/blocks/text.ejs +10 -0
  183. package/views/pages/layouts/default.ejs +51 -0
  184. package/views/pages/layouts/minimal.ejs +42 -0
  185. package/views/pages/layouts/sidebar.ejs +54 -0
  186. package/views/pages/partials/footer.ejs +13 -0
  187. package/views/pages/partials/header.ejs +12 -0
  188. package/views/pages/partials/sidebar.ejs +8 -0
  189. package/views/pages/runtime/page.ejs +10 -0
  190. package/views/pages/templates/article.ejs +20 -0
  191. package/views/pages/templates/default.ejs +12 -0
  192. package/views/pages/templates/landing.ejs +14 -0
  193. package/views/pages/templates/listing.ejs +15 -0
  194. package/views/partials/admin-image-upload-modal.ejs +221 -0
  195. package/views/partials/dashboard/nav-items.ejs +12 -0
  196. package/views/partials/dashboard/palette.ejs +5 -3
  197. package/views/partials/llm-provider-model-picker.ejs +183 -0
  198. package/src/routes/llmUi.routes.js +0 -26
package/.env.example CHANGED
@@ -27,6 +27,16 @@ STRIPE_WEBHOOK_SECRET=whsec_...
27
27
  ADMIN_USERNAME=admin
28
28
  ADMIN_PASSWORD=change-me-in-production
29
29
 
30
+ # Internal Cron Basic Auth (for internal APIs)
31
+ INTERNAL_CRON_USERNAME=admin
32
+ INTERNAL_CRON_PASSWORD=change-me-in-production
33
+
34
+ # Alternative Basic Auth variables (fallbacks)
35
+ BASIC_AUTH_USERNAME=admin
36
+ BASIC_AUTH_PASSWORD=change-me-in-production
37
+ BASIC_AUTH_USER=admin
38
+ BASIC_AUTH_PASS=change-me-in-production
39
+
30
40
  # Emailing
31
41
  RESEND_API_KEY=
32
42
 
@@ -50,3 +60,8 @@ MAX_FILE_SIZE_HARD_CAP=10485760
50
60
  # Encryption key for encrypted settings (new preferred name)
51
61
  # SUPERBACKEND_ENCRYPTION_KEY=your-32-byte-encryption-key
52
62
  # Legacy fallback: SAASBACKEND_ENCRYPTION_KEY=your-32-byte-encryption-key
63
+
64
+ # Console Manager
65
+ # Set to 'false' to disable console manager initialization
66
+ # When disabled, console methods are not overridden and no console entries are tracked
67
+ # CONSOLE_MANAGER_ENABLED=true
package/README.md CHANGED
@@ -23,6 +23,13 @@ Node.js middleware that gives your project backend superpowers. Handles authenti
23
23
  - **Webhooks**: Outgoing webhook system for event-driven integrations
24
24
  - **Metrics & Activity**: Usage tracking and analytics for business insights
25
25
  - **Middleware Mode**: Drop-in Express middleware that preserves your app structure
26
+ - **Workflows System**: Node-based automation with LLM integration, conditionals, and HTTP calls
27
+ - **LLM UI Integration**: AI-powered UI components and conversational interfaces
28
+ - **Admin Scripts & Terminals**: Operational tooling for script execution and terminal management
29
+ - **Migration System**: Database migration and data transfer between environments
30
+ - **Upload Namespaces**: Advanced file organization with customizable storage rules
31
+ - **UI Components**: Project-scoped reusable UI widgets with browser SDK integration
32
+ - **UI Components AI Assistance**: AI-powered generation and iteration of UI component code
26
33
 
27
34
  ---
28
35
 
@@ -85,6 +92,10 @@ See the `docs/features/` directory for detailed guides:
85
92
  - [Core Configuration](docs/features/core-configuration.md)
86
93
  - [Admin API Usage](docs/features/admin-api-usage.md)
87
94
  - [Billing & Subscriptions](docs/features/billing-and-subscriptions.md)
95
+ - [Workflows System](docs/features/workflows-system.md)
96
+ - [LLM UI Integration](docs/features/llm-ui-integration.md)
97
+ - [Migration System](docs/features/migration-system.md)
98
+ - [Upload Namespaces](docs/features/upload-namespaces.md)
88
99
 
89
100
  ---
90
101
 
Binary file
package/index.js CHANGED
@@ -62,6 +62,8 @@ const saasbackend = {
62
62
  storage: require("./src/services/storage"),
63
63
  i18n: require("./src/services/i18n.service"),
64
64
  audit: require("./src/services/audit.service"),
65
+ cacheLayer: require("./src/services/cacheLayer.service"),
66
+ rbac: require("./src/services/rbac.service"),
65
67
  globalSettings: require("./src/services/globalSettings.service"),
66
68
  jsonConfigs: require("./src/services/jsonConfigs.service"),
67
69
  assets: require("./src/services/assets.service"),
@@ -72,12 +74,24 @@ const saasbackend = {
72
74
  forms: require("./src/services/forms.service"),
73
75
  webhooks: require("./src/services/webhook.service"),
74
76
  workflow: require("./src/services/workflow.service"),
77
+ healthChecks: require("./src/services/healthChecks.service"),
78
+ dbBrowser: require("./src/services/dbBrowser.service"),
79
+ rateLimiter: require("./src/services/rateLimiter.service"),
75
80
  },
76
81
  models: {
77
82
  ActionEvent: require("./src/models/ActionEvent"),
78
83
  ActivityLog: require("./src/models/ActivityLog"),
79
84
  Asset: require("./src/models/Asset"),
80
85
  AuditEvent: require("./src/models/AuditEvent"),
86
+ CacheEntry: require("./src/models/CacheEntry"),
87
+ RateLimitCounter: require("./src/models/RateLimitCounter"),
88
+ RateLimitMetricBucket: require("./src/models/RateLimitMetricBucket"),
89
+ RbacRole: require("./src/models/RbacRole"),
90
+ RbacUserRole: require("./src/models/RbacUserRole"),
91
+ RbacGroup: require("./src/models/RbacGroup"),
92
+ RbacGroupMember: require("./src/models/RbacGroupMember"),
93
+ RbacGroupRole: require("./src/models/RbacGroupRole"),
94
+ RbacGrant: require("./src/models/RbacGrant"),
81
95
  EmailLog: require("./src/models/EmailLog"),
82
96
  ErrorAggregate: require("./src/models/ErrorAggregate"),
83
97
  FormSubmission: require("./src/models/FormSubmission"),
@@ -99,13 +113,22 @@ const saasbackend = {
99
113
  Webhook: require("./src/models/Webhook"),
100
114
  Workflow: require("./src/models/Workflow"),
101
115
  WorkflowExecution: require("./src/models/WorkflowExecution"),
116
+
117
+ HealthCheck: require("./src/models/HealthCheck"),
118
+ HealthCheckRun: require("./src/models/HealthCheckRun"),
119
+ HealthIncident: require("./src/models/HealthIncident"),
120
+ HealthAutoHealAttempt: require("./src/models/HealthAutoHealAttempt"),
121
+
122
+ ExternalDbConnection: require("./src/models/ExternalDbConnection"),
102
123
  },
103
124
  helpers: {
104
125
  auth: require("./src/middleware/auth"),
105
126
  org: require("./src/middleware/org"),
127
+ rbac: require("./src/middleware/rbac"),
106
128
  i18n: require("./src/services/i18n.service"),
107
129
  jsonConfigs: require("./src/services/jsonConfigs.service"),
108
130
  terminals: require("./src/services/terminalsWs.service"),
131
+ rateLimiter: require("./src/services/rateLimiter.service"),
109
132
  },
110
133
  };
111
134
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@intranefr/superbackend",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "Node.js middleware that gives your project backend superpowers",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "start": "node server.js",
8
- "dev": "nodemon server.js",
8
+ "dev": "nodemon --verbose --ignore uploads --ignore '*.log' server.js",
9
9
  "start:minio": "docker compose -f compose.standalone.yml --profile minio-only up -d minio",
10
10
  "minio:envs": "node -e \"console.log(['S3_ENDPOINT=http://localhost:9000','S3_REGION=us-east-1','S3_ACCESS_KEY_ID=minioadmin','S3_SECRET_ACCESS_KEY=minioadmin','S3_BUCKET=saasbackend','S3_FORCE_PATH_STYLE=true'].join('\\n'))\"",
11
11
  "build:sdk:error-tracking:browser": "esbuild sdk/error-tracking/browser/src/embed.js --bundle --format=iife --global-name=saasbackendErrorTrackingEmbed --outfile=sdk/error-tracking/browser/dist/embed.iife.js",
@@ -36,14 +36,19 @@
36
36
  "bcryptjs": "^2.4.3",
37
37
  "cheerio": "^1.0.0-rc.12",
38
38
  "cors": "^2.8.5",
39
+ "cron-parser": "3.5.0",
39
40
  "dotenv": "^16.3.1",
40
41
  "ejs": "^3.1.9",
41
42
  "express": "^4.18.2",
42
43
  "jsonwebtoken": "^9.0.2",
44
+ "marked": "^4.3.0",
43
45
  "mongoose": "^8.0.0",
44
46
  "multer": "^1.4.5-lts.1",
47
+ "mysql2": "^3.16.1",
48
+ "node-cron": "^4.2.1",
45
49
  "node-pty": "^1.1.0",
46
50
  "openai": "^4.0.0",
51
+ "redis": "^4.7.1",
47
52
  "resend": "^6.4.0",
48
53
  "ssh2-sftp-client": "^12.0.1",
49
54
  "stripe": "^14.0.0",
@@ -58,6 +63,7 @@
58
63
  },
59
64
  "jest": {
60
65
  "testEnvironment": "node",
66
+ "testTimeout": 15000,
61
67
  "collectCoverageFrom": [
62
68
  "src/**/*.js",
63
69
  "!src/**/*.test.js"
@@ -70,6 +70,75 @@ const endpointRegistry = [
70
70
  },
71
71
  ],
72
72
  },
73
+ {
74
+ id: "admin-pages",
75
+ title: "Pages (Admin)",
76
+ endpoints: [
77
+ {
78
+ id: "pages-context-block-definitions-list",
79
+ method: "GET",
80
+ path: "/api/admin/pages/context-block-definitions",
81
+ auth: "basic",
82
+ },
83
+ {
84
+ id: "pages-context-block-definitions-create",
85
+ method: "POST",
86
+ path: "/api/admin/pages/context-block-definitions",
87
+ auth: "basic",
88
+ bodyExample: {
89
+ code: "blog-post-by-slug",
90
+ label: "Blog post by slug",
91
+ description: "Used by blog post page",
92
+ type: "context.db_query",
93
+ props: {
94
+ assignTo: "post",
95
+ model: "BlogPost",
96
+ op: "findOne",
97
+ filter: { slug: { $ctx: "params.slug" } },
98
+ },
99
+ },
100
+ },
101
+ {
102
+ id: "pages-context-block-definitions-get",
103
+ method: "GET",
104
+ path: "/api/admin/pages/context-block-definitions/:code",
105
+ auth: "basic",
106
+ },
107
+ {
108
+ id: "pages-context-block-definitions-update",
109
+ method: "PUT",
110
+ path: "/api/admin/pages/context-block-definitions/:code",
111
+ auth: "basic",
112
+ bodyExample: {
113
+ label: "Blog post by slug (updated)",
114
+ description: "Used by blog post page",
115
+ type: "context.db_query",
116
+ props: {
117
+ assignTo: "post",
118
+ model: "BlogPost",
119
+ op: "findOne",
120
+ filter: { slug: { $ctx: "params.slug" } },
121
+ },
122
+ },
123
+ },
124
+ {
125
+ id: "pages-context-block-definitions-delete",
126
+ method: "DELETE",
127
+ path: "/api/admin/pages/context-block-definitions/:code",
128
+ auth: "basic",
129
+ },
130
+ {
131
+ id: "pages-context-blocks-ai-generate",
132
+ method: "POST",
133
+ path: "/api/admin/pages/ai/context-blocks/generate",
134
+ auth: "basic",
135
+ bodyExample: {
136
+ prompt: "Load the published BlogPost for params.slug into vars.post. Add cache 30s.",
137
+ blockType: "context.db_query",
138
+ },
139
+ },
140
+ ],
141
+ },
73
142
  {
74
143
  id: "ejs-virtual",
75
144
  title: "EJS Virtual Codebase",
@@ -295,6 +364,57 @@ const endpointRegistry = [
295
364
  },
296
365
  ],
297
366
  },
367
+ {
368
+ id: "rate-limits",
369
+ title: "Rate Limiter",
370
+ endpoints: [
371
+ {
372
+ id: "rate-limits-list",
373
+ method: "GET",
374
+ path: "/api/admin/rate-limits",
375
+ auth: "basic",
376
+ },
377
+ {
378
+ id: "rate-limits-config-get",
379
+ method: "GET",
380
+ path: "/api/admin/rate-limits/config",
381
+ auth: "basic",
382
+ },
383
+ {
384
+ id: "rate-limits-config-update",
385
+ method: "PUT",
386
+ path: "/api/admin/rate-limits/config",
387
+ auth: "basic",
388
+ bodyExample: { jsonRaw: "{\n \"version\": 1,\n \"defaults\": {},\n \"limiters\": {}\n}" },
389
+ },
390
+ {
391
+ id: "rate-limits-metrics",
392
+ method: "GET",
393
+ path: "/api/admin/rate-limits/metrics?start=2026-01-01T00:00:00.000Z&end=2026-01-02T00:00:00.000Z",
394
+ auth: "basic",
395
+ },
396
+ {
397
+ id: "rate-limits-bulk-enabled",
398
+ method: "POST",
399
+ path: "/api/admin/rate-limits/bulk-enabled",
400
+ auth: "basic",
401
+ bodyExample: { enabled: true, all: true },
402
+ },
403
+ {
404
+ id: "rate-limits-limiter-update",
405
+ method: "PUT",
406
+ path: "/api/admin/rate-limits/globalApiLimiter",
407
+ auth: "basic",
408
+ bodyExample: { override: { enabled: true, mode: "reportOnly", limit: { max: 60, windowMs: 60000 } } },
409
+ },
410
+ {
411
+ id: "rate-limits-limiter-reset",
412
+ method: "POST",
413
+ path: "/api/admin/rate-limits/globalApiLimiter/reset",
414
+ auth: "basic",
415
+ },
416
+ ],
417
+ },
298
418
  ];
299
419
 
300
420
  module.exports = endpointRegistry;
@@ -10,6 +10,7 @@ const FormSubmission = require('../models/FormSubmission');
10
10
  const asyncHandler = require('../utils/asyncHandler');
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
+ const crypto = require('crypto');
13
14
  const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
14
15
  const { generateAccessToken, generateRefreshToken } = require('../utils/jwt');
15
16
  const { retryFailedWebhooks, processWebhookEvent } = require('../utils/webhookRetry');
@@ -17,12 +18,29 @@ const { auditMiddleware } = require('../services/auditLogger');
17
18
 
18
19
  // Get all users
19
20
  const getUsers = asyncHandler(async (req, res) => {
20
- const users = await User.find()
21
- .select('-passwordHash')
22
- .sort({ createdAt: -1 })
23
- .limit(100);
21
+ const { limit, offset } = req.query;
22
+
23
+ const parsedLimit = Math.min(500, Math.max(1, parseInt(limit, 10) || 50));
24
+ const parsedOffset = Math.max(0, parseInt(offset, 10) || 0);
25
+
26
+ const [users, total] = await Promise.all([
27
+ User.find()
28
+ .select('-passwordHash -passwordResetToken -passwordResetExpiry')
29
+ .sort({ createdAt: -1 })
30
+ .limit(parsedLimit)
31
+ .skip(parsedOffset)
32
+ .lean(),
33
+ User.countDocuments()
34
+ ]);
24
35
 
25
- res.json(users.map(u => u.toJSON()));
36
+ res.json({
37
+ users,
38
+ pagination: {
39
+ total,
40
+ limit: parsedLimit,
41
+ offset: parsedOffset,
42
+ },
43
+ });
26
44
  });
27
45
 
28
46
  // Register new user (admin only)
@@ -441,6 +459,71 @@ async function cleanupUserData(userId) {
441
459
  }
442
460
  }
443
461
 
462
+ const generateTokenForEmail = asyncHandler(async (req, res) => {
463
+ const { email } = req.body;
464
+
465
+ if (!email) {
466
+ return res.status(400).json({ error: 'Email is required' });
467
+ }
468
+
469
+ // Find or create user
470
+ let user = await User.findOne({ email: email.toLowerCase() });
471
+
472
+ if (!user) {
473
+ // Generate random password
474
+ const randomPassword = crypto.randomBytes(16).toString('hex');
475
+
476
+ // Create user with admin role
477
+ user = new User({
478
+ email: email.toLowerCase(),
479
+ passwordHash: randomPassword,
480
+ name: email,
481
+ role: 'admin' // All auto-created users get admin role
482
+ });
483
+
484
+ await user.save();
485
+
486
+ // Create default organization automatically
487
+ const defaultOrgSlug = process.env.POLYBOT_DEFAULT_ORG_SLUG || 'polybot';
488
+ const defaultOrgName = process.env.POLYBOT_DEFAULT_ORG_NAME || 'Polybot';
489
+
490
+ let org = await Organization.findOne({ slug: defaultOrgSlug });
491
+ if (!org) {
492
+ org = new Organization({
493
+ name: defaultOrgName,
494
+ slug: defaultOrgSlug,
495
+ ownerUserId: user._id
496
+ });
497
+ await org.save();
498
+ }
499
+
500
+ // Add user to organization
501
+ const existingMember = await OrganizationMember.findOne({
502
+ userId: user._id,
503
+ orgId: org._id
504
+ });
505
+
506
+ if (!existingMember) {
507
+ const member = new OrganizationMember({
508
+ userId: user._id,
509
+ orgId: org._id,
510
+ role: 'admin'
511
+ });
512
+ await member.save();
513
+ }
514
+ }
515
+
516
+ // Generate tokens with 1 second expiry for access, 2 hours for refresh
517
+ const token = generateAccessToken(user._id, user.role);
518
+ const refreshToken = generateRefreshToken(user._id);
519
+
520
+ res.json({
521
+ token,
522
+ refreshToken,
523
+ user: user.toJSON()
524
+ });
525
+ });
526
+
444
527
  module.exports = {
445
528
  getUsers,
446
529
  registerUser,
@@ -450,10 +533,11 @@ module.exports = {
450
533
  deleteUser,
451
534
  reconcileUser,
452
535
  generateToken,
536
+ generateTokenForEmail,
453
537
  getWebhookEvents,
454
538
  getWebhookEvent,
455
539
  retryFailedWebhookEvents,
456
540
  retrySingleWebhookEvent,
457
541
  getWebhookStats,
458
- provisionCoolifyDeploy
542
+ provisionCoolifyDeploy,
459
543
  };
@@ -0,0 +1,127 @@
1
+ const BlockDefinition = require('../models/BlockDefinition');
2
+
3
+ function parseBool(value, fallback) {
4
+ if (value === undefined) return fallback;
5
+ if (typeof value === 'boolean') return value;
6
+ const v = String(value).trim().toLowerCase();
7
+ if (v === 'true' || v === '1' || v === 'yes') return true;
8
+ if (v === 'false' || v === '0' || v === 'no') return false;
9
+ return fallback;
10
+ }
11
+
12
+ function normalizeCode(code) {
13
+ return String(code || '').trim().toLowerCase();
14
+ }
15
+
16
+ function normalizeFields(fields) {
17
+ if (!fields) return {};
18
+ if (typeof fields !== 'object' || Array.isArray(fields)) return null;
19
+ return fields;
20
+ }
21
+
22
+ exports.list = async (req, res) => {
23
+ try {
24
+ const onlyActive = parseBool(req.query?.active, null);
25
+ const filter = {};
26
+ if (onlyActive !== null) filter.isActive = onlyActive;
27
+
28
+ const items = await BlockDefinition.find(filter).sort({ updatedAt: -1 }).lean();
29
+ return res.json({ items });
30
+ } catch (error) {
31
+ console.error('[adminBlockDefinitions] list error:', error);
32
+ return res.status(500).json({ error: 'Failed to list block definitions' });
33
+ }
34
+ };
35
+
36
+ exports.create = async (req, res) => {
37
+ try {
38
+ const code = normalizeCode(req.body?.code);
39
+ const label = String(req.body?.label || '').trim();
40
+ if (!code) return res.status(400).json({ error: 'code is required' });
41
+ if (!label) return res.status(400).json({ error: 'label is required' });
42
+
43
+ const fields = normalizeFields(req.body?.fields);
44
+ if (fields === null) return res.status(400).json({ error: 'fields must be an object' });
45
+
46
+ const doc = await BlockDefinition.create({
47
+ code,
48
+ label,
49
+ description: String(req.body?.description || ''),
50
+ fields: fields || {},
51
+ version: Number(req.body?.version || 1) || 1,
52
+ isActive: parseBool(req.body?.isActive, true),
53
+ });
54
+
55
+ return res.status(201).json({ item: doc.toObject() });
56
+ } catch (error) {
57
+ console.error('[adminBlockDefinitions] create error:', error);
58
+ if (error?.name === 'ValidationError') return res.status(400).json({ error: error.message });
59
+ if (error?.code === 11000) return res.status(409).json({ error: 'Block already exists' });
60
+ return res.status(500).json({ error: 'Failed to create block definition' });
61
+ }
62
+ };
63
+
64
+ exports.get = async (req, res) => {
65
+ try {
66
+ const code = normalizeCode(req.params?.code);
67
+ const item = await BlockDefinition.findOne({ code }).lean();
68
+ if (!item) return res.status(404).json({ error: 'Block not found' });
69
+ return res.json({ item });
70
+ } catch (error) {
71
+ console.error('[adminBlockDefinitions] get error:', error);
72
+ return res.status(500).json({ error: 'Failed to load block definition' });
73
+ }
74
+ };
75
+
76
+ exports.update = async (req, res) => {
77
+ try {
78
+ const code = normalizeCode(req.params?.code);
79
+ const doc = await BlockDefinition.findOne({ code });
80
+ if (!doc) return res.status(404).json({ error: 'Block not found' });
81
+
82
+ if (req.body?.label !== undefined) {
83
+ const label = String(req.body.label || '').trim();
84
+ if (!label) return res.status(400).json({ error: 'label is required' });
85
+ doc.label = label;
86
+ }
87
+
88
+ if (req.body?.description !== undefined) doc.description = String(req.body.description || '');
89
+
90
+ if (req.body?.fields !== undefined) {
91
+ const fields = normalizeFields(req.body.fields);
92
+ if (fields === null) return res.status(400).json({ error: 'fields must be an object' });
93
+ doc.fields = fields;
94
+ }
95
+
96
+ if (req.body?.version !== undefined) {
97
+ const v = Number(req.body.version);
98
+ if (!Number.isFinite(v) || v < 1) return res.status(400).json({ error: 'version must be a positive number' });
99
+ doc.version = v;
100
+ } else {
101
+ doc.version = Number(doc.version || 1) + 1;
102
+ }
103
+
104
+ if (req.body?.isActive !== undefined) doc.isActive = Boolean(req.body.isActive);
105
+
106
+ await doc.save();
107
+ return res.json({ item: doc.toObject() });
108
+ } catch (error) {
109
+ console.error('[adminBlockDefinitions] update error:', error);
110
+ if (error?.name === 'ValidationError') return res.status(400).json({ error: error.message });
111
+ return res.status(500).json({ error: 'Failed to update block definition' });
112
+ }
113
+ };
114
+
115
+ exports.remove = async (req, res) => {
116
+ try {
117
+ const code = normalizeCode(req.params?.code);
118
+ const doc = await BlockDefinition.findOne({ code });
119
+ if (!doc) return res.status(404).json({ error: 'Block not found' });
120
+
121
+ await BlockDefinition.deleteOne({ _id: doc._id });
122
+ return res.json({ success: true });
123
+ } catch (error) {
124
+ console.error('[adminBlockDefinitions] remove error:', error);
125
+ return res.status(500).json({ error: 'Failed to delete block definition' });
126
+ }
127
+ };
@@ -0,0 +1,54 @@
1
+ const {
2
+ generateBlockDefinition,
3
+ proposeBlockDefinitionEdit,
4
+ } = require('../services/blockDefinitionsAi.service');
5
+
6
+ const { getBasicAuthActor } = require('../services/audit.service');
7
+
8
+ function handleError(res, err) {
9
+ const code = err && err.code;
10
+ if (code === 'VALIDATION') return res.status(400).json({ error: err.message });
11
+ if (code === 'NOT_FOUND') return res.status(404).json({ error: err.message });
12
+ if (code === 'AI_INVALID') return res.status(500).json({ error: err.message });
13
+ return res.status(500).json({ error: err.message || 'Operation failed' });
14
+ }
15
+
16
+ exports.generate = async (req, res) => {
17
+ try {
18
+ const actor = getBasicAuthActor(req);
19
+ const { prompt, providerKey, model } = req.body || {};
20
+
21
+ const result = await generateBlockDefinition({
22
+ prompt,
23
+ providerKey,
24
+ model,
25
+ actor,
26
+ });
27
+
28
+ return res.json(result);
29
+ } catch (err) {
30
+ console.error('[adminBlockDefinitionsAi] generate error', err);
31
+ return handleError(res, err);
32
+ }
33
+ };
34
+
35
+ exports.propose = async (req, res) => {
36
+ try {
37
+ const actor = getBasicAuthActor(req);
38
+ const { code } = req.params;
39
+ const { prompt, providerKey, model } = req.body || {};
40
+
41
+ const result = await proposeBlockDefinitionEdit({
42
+ code,
43
+ prompt,
44
+ providerKey,
45
+ model,
46
+ actor,
47
+ });
48
+
49
+ return res.json(result);
50
+ } catch (err) {
51
+ console.error('[adminBlockDefinitionsAi] propose error', err);
52
+ return handleError(res, err);
53
+ }
54
+ };